#include "TrueTypeTextImpl.h"
#include "TextRenderInfo.h"
#include <lib3d/CameraDesc.h>

#include "font/TrueType.h"

#include "TrueText/BitmapInfo.h"
#include "TrueText/Font.h"
#include "TrueText/Text.h"
#include "TrueText/TextBounds.h"
#include "TrueText/Types.h"

#include <math/utl.h>

#ifdef IPHONE_VERSION
extern bool isRetinaSupported();
#endif

namespace liba {
namespace lib3d {
namespace node {

struct CachedText
{
	CachedText(const TrueText::Text& t)
		: text(t)
	{
	}

	TrueText::Text text;
};

TrueTypeTextImpl::TrueTypeTextImpl(font::TrueType* font) 
	: font(font)
	, last_camera(NULL)
	, valignment(VA_BOTTOM_BASELINE)
{
	hard::Manager::add_dependent(this);
}
	
TrueTypeTextImpl::TrueTypeTextImpl(const TrueTypeTextImpl & other) : TextImpl(other), font(other.font->clone()), last_camera(NULL), needRebuild(true), cachedText(NULL)
{
	hard::Manager::add_dependent(this);
}
	
TrueTypeTextImpl * TrueTypeTextImpl::clone()
{
	return new TrueTypeTextImpl(*this);
}

TrueTypeTextImpl::~TrueTypeTextImpl()
{
	hard::Manager::remove_dependent(this);
}

void TrueTypeTextImpl::change_valignment(VAlignment value)
{
	this->valignment = value;
	this->needRebuild = true;
}

void TrueTypeTextImpl::on_add_hardware(hard::Manager::hardware_type * ha)
{
	std::auto_ptr<TrueTypeTextData> data(new TrueTypeTextData());
	data->vb.reset(ha->create_vertex_buffer());
	data->needRebuild = true;
	
	hard_vector.push_back( SweetPair(ha, data.release()) );
}

void TrueTypeTextImpl::render_to_list( hard::Hardware * hardware, TextRenderInfo* tri, hard::RenderList * render_list )
{
	hard::RenderModel render_model;
	
	render_model.material.diffuse = tri->color;
	render_model.material.emissive = tri->color;
	tri->transparency = math::clamp( tri->transparency, 0.0001f, 0.9999f );
	render_model.material.transparency = tri->transparency;

	render_model.world = tri->total_matrix;

	TrueTypeTextData* data = find_data(hardware);
	if( !data->vb->is_valid() || !data->tex.get() || data->needRebuild || last_camera != Camera::active_camera())
		rebuildHardware(hardware);

	render_model.vb = data->vb.get();
	render_model.primitive_start = 0;
	render_model.primitive_count = 2;
	render_model.ib = 0;
	render_model.vertex_start = 0;
	render_model.vertex_count = 6;

	render_model.material.emissive_texture.texture = data->tex.get();
	render_model.material.emissive_texture.wrap = false;
	render_model.material.emissive_texture.alpha_channel = true;

	render_list->models.push_back( render_model );
}

float TrueTypeTextImpl::get_text_width(const std::wstring & text)
{
	if (needRebuild)
		rebuildText();
	return cachedText->text.GetBounds().GetDimensions().Width;
}

float TrueTypeTextImpl::get_text_height()
{
	if (needRebuild)
		rebuildText();
	return cachedText->text.GetBounds().GetDimensions().Height;
}
	
void TrueTypeTextImpl::text_data_changed()
{
	needRebuild = true;

	HardVector::iterator it = hard_vector.begin();
	for( ;it != hard_vector.end(); ++it )
	{
		it->second->needRebuild = true;
	}
}

class TextureLock
{
public:
	TextureLock(hard::Texture* tex)
		: tex(tex)
	{
		ptr_ = tex->lock(0, &stride_);
	}

	~TextureLock()
	{
		tex->unlock(0);
	}

	void* buffer() { return ptr_; }
	int stride() { return stride_; }
private:
	int stride_;
	void* ptr_;
	hard::Texture* tex;
};

void TrueTypeTextImpl::rebuildText()
{
	if (!needRebuild)
		return;

	TrueText::Text text(*font->Font());
	text.SetTextAlign((TrueText::TextAlign)GetTextData()->GetAlignment());
	this->change_valignment(GetTextData()->GetVAlignment());
	text.SetString(GetTextData()->GetText().c_str());
	if (textData->GetMaxWidth())
		text.SetClipWidth(textData->GetMaxWidth());

	cachedText.reset(new CachedText(text));
}

static int getPowerOf2(int v)
{
	v--;
	v |= v >> 1;
	v |= v >> 2;
	v |= v >> 4;
	v |= v >> 8;
	v |= v >> 16;
	v++;

	return v;
}

float DPI = 72.0f;

void TrueTypeTextImpl::rebuildHardware(hard::Hardware* ha)
{
	TrueTypeTextData* data = find_data(ha);

	if (needRebuild)
		rebuildText();

	TrueText::TextBounds bounds = cachedText->text.GetBounds();
	TrueText::Dimensions dims = bounds.GetDimensions();

	TrueText::Point offset;
	offset.y = font->Font()->GetHeight() - bounds.GetTopBaselineDistance();
	dims.Height += offset.y;

	int dstWidth = getPowerOf2(dims.Width);
	int dstHeight = getPowerOf2(dims.Height);

	std::auto_ptr<hard::Texture> tex(ha->create_texture(0, pictureformat::A8R8G8B8, dstWidth, dstHeight));
	{
		TextureLock data(tex.get());

		//clear data buffer first
		for(int i = 0; i < dstHeight; ++i)
		{
			//Texture is always PIXEL_FORMAT_ARGB_8888
			memset(((char *)data.buffer()) + i*data.stride(), 0, dstWidth*4);
		}

		TrueText::BitmapInfo bi;
		bi.SetBits(data.buffer());
		bi.SetPitch(data.stride());
		bi.SetPixelFormat(TrueText::PIXEL_FORMAT_ARGB_8888);
		bi.SetBounds(dims);

		cachedText->text.Render(bi, offset);
	}
	
	// compute number of lines
	int lines = 1;
	std::wstring textString = cachedText->text.GetString();
	for(size_t i = 0; i < textString.length(); i++)
	{
		if(textString[i] == '\n')
			lines++;
	}
	
	
	last_camera = Camera::active_camera();
	bool isOrthoProjection = last_camera ? last_camera->ortho_projection() : false;
	const float blockWidth  = ((float)dims.Width  / (isOrthoProjection ? 1.0f : DPI));
	const float blockHeight = ((float)dims.Height / (isOrthoProjection ? 1.0f : DPI));
	float current_x = 0;

	float current_y = 0;

	switch (this->valignment)
	{
	case VA_TOP:
		current_y = -blockHeight;
		break;
	case VA_MIDDLE:
		current_y = -blockHeight / 2;
		break;
	case VA_BOTTOM_BASELINE:
		current_y = -(float)bounds.GetBottomBaselineDistance();
		break;
	case VA_BOTTOM_BLOCK:
		current_y = 0.0f;
		break;
	};

	switch( GetTextData()->GetAlignment() )
	{
	case TextData::CENTER:
		current_x = (blockWidth / 2) * (isOrthoProjection ? -1 : 1);
		break;
	case TextData::RIGHT:
		current_x = blockWidth * (isOrthoProjection ? -1 : 1);
		break;
	}

	SimpleArray<float> positions(6 * 3); float* pos_data = positions.data();
	SimpleArray<float> uvs(6 * 2); float* uvs_data = uvs.data();
	
	/*const float dx = -dims.Width * koef; // may need scaling
	const float dy = dims.Height * koef; // may need scaling
	const float x = current_x;*/
	const float dx = (isOrthoProjection ? blockWidth : -blockWidth);
	const float dy = blockHeight;
	const float x = current_x;
	const float y = current_y;

	/*const float du = 1;
	const float dv = -1;
	const float u = 0.0f;
	const float v = 1.0f;*/

	const float du = (double)dims.Width / (double)dstWidth;
	const float dv = -((double)dims.Height / (double)dstHeight);
	const float u = 0.0f;
	const float v = (double)dims.Height / (double)dstHeight;

	*pos_data++ = x;
	*pos_data++ = y;
	*pos_data++ = 0;
	*uvs_data++ = u;
	*uvs_data++ = v;

	*pos_data++ = x;
	*pos_data++ = y + dy;
	*pos_data++ = 0;
	*uvs_data++ = u;
	*uvs_data++ = v + dv;

	*pos_data++ = x + dx;
	*pos_data++ = y;
	*pos_data++ = 0;
	*uvs_data++ = u + du;
	*uvs_data++ = v;

	*pos_data++ = x + dx;
	*pos_data++ = y;
	*pos_data++ = 0;
	*uvs_data++ = u + du;
	*uvs_data++ = v;

	*pos_data++ = x;
	*pos_data++ = y + dy;
	*pos_data++ = 0;
	*uvs_data++ = u;
	*uvs_data++ = v + dv;

	*pos_data++ = x + dx;
	*pos_data++ = y + dy;
	*pos_data++ = 0;
	*uvs_data++ = u + du;
	*uvs_data++ = v + dv;

	SimpleArray<float> empty_float;
	SimpleArray<int> empty_int;

	data->vb->update(positions, empty_float, empty_float, uvs, uvs);
	data->tex = tex;

	data->needRebuild = false;
}

} // namespace node
} // namespace lib3d
} // namespace liba